require 'zip/zip'
require 'key'

class PersistentDB
  
  PATH_BASE = ""

  attr_accessor :node_id
  attr_accessor :associated_conn
  attr_accessor :abandoned_time
  
  def initialize(node_id, base_path_complement = "")
    @lock = Mutex.new
    @associated_conn = nil
    @abandoned_time = nil
    @node_id = node_id
    @path_id = PATH_BASE + base_path_complement + node_id + "/"
    @base_path_complement = base_path_complement
    begin
       Dir.mkdir(@path_id)
    rescue Exception => e
      puts "\n"
      puts e.backtrace.join("\n")
      puts "[PersistentDB] Erro ao criar diretorio do banco: " + e.message
    end
  end
  
  def insert_file(document_id, document)
    #@lock.lock
    begin
      if File.exist?(@path_id + document_id)
        return false
      end
      file = File.open(@path_id + document_id, 'wb')
      file.write(document)
      file.close
      return true
    ensure
      #@lock.unlock
    end    
  end
  
  def remove_file(document_id)
    #@lock.lock
    begin
      if File.exist?(@path_id + document_id)
        File.delete(@path_id + document_id)
        return true
      end
      return false
    ensure
      #@lock.unlock
    end    
  end
  
  def update_file(document_id, document)
    #@lock.lock
    begin
      if File.exist?(@path_id + document_id)
        File.delete(@path_id + document_id)
        insert_file(document_id, document)
        return true
      end
      return false
    ensure
      #@lock.unlock
    end    
  end
  
  def get_file(document_id)
    #@lock.lock
    begin
      if File.exist?(@path_id + document_id)
        file = File.open(@path_id + document_id, 'rb')
        begin
          return file.read
        ensure
          file.close
        end
      end
      return nil
    ensure
      #@lock.unlock
    end    
  end

  def get_all_files
    #@lock.lock
    begin
      begin
        if File.exist?(@path_id + "all_files.zip")
          File.delete(@path_id + "all_files.zip")
        end

        zip = Zip::ZipFile.new(@path_id + "all_files.zip", true)
        zip.add(@node_id + "/", @path_id)

        Dir.entries(@path_id).each  do |file|
          if file != '.' and file != '..'  and file != 'all_files.zip'
            zip.add(@node_id + "/" + file, @path_id + file)
          end
        end

        zip.commit
        zip.close

        zip_file = File.open(@path_id + "all_files.zip", "rb")
        zip_buff = zip_file.read
        zip_file.close

        File.delete(@path_id + "all_files.zip")

        return zip_buff
      rescue Exception => e
        puts "\n"
        puts e.backtrace.join("\n")
        puts "[PersistentDB] Erro ao zipar todos os arquivos do banco: " + e.message
        return nil
      end
    ensure
      #@lock.unlock
    end    
  end

  # Retorna todos os arquivos do banco que (estao/nao estao) no intervalo passado.
  def get_all_files_range(start_key, end_key, include_start_key = true, include_end_key = true, in_range = true)
    #@lock.lock
    begin
      begin
        if File.exist?(@path_id + "all_files.zip")
          File.delete(@path_id + "all_files.zip")
        end

        zip = Zip::ZipFile.new(@path_id + "all_files.zip", true)
        zip.add("documents", @path_id)

        Dir.entries(@path_id).each  do |file|
          if file != '.' and file != '..'  and file != 'all_files.zip'
            key = Key.new("")
            key.set_key_by_string(file)

            is_in_range = key.is_in_range(start_key, end_key, include_start_key, include_end_key)

            if (in_range && is_in_range) || (!in_range && !is_in_range)
              zip.add("documents/" + file, @path_id + file)
            end
          end
        end

        zip.commit
        zip.close

        zip_file = File.open(@path_id + "all_files.zip", "rb")
        zip_buff = zip_file.read
        zip_file.close

        File.delete(@path_id + "all_files.zip")

        return zip_buff
      rescue Exception => e
        puts "\n"
        puts e.backtrace.join("\n")
        puts "[PersistentDB] Erro ao zipar todos os arquivos do banco: " + e.message
        return nil
      end
    ensure
      #@lock.unlock
    end    
  end

  def remove_all_files
    #@lock.lock
    begin
      begin
        Dir.entries(@path_id).each  do |file|
          if file != '.' and file != '..'
            File.delete(@path_id + file)
          end
        end
      rescue Exception => e
        puts "\n"
        puts e.backtrace.join("\n")
        puts "[PersistentDB] Erro ao remover todos os arquivos."
        return false
      end
      return true
    ensure
      #@lock.unlock
    end    
  end

  # Remove todos os arquivos do banco que (estao/nao estao) na faixa passada.
  def remove_all_files_range(start_key, end_key, include_start_key = true, include_end_key = true, in_range = true)
    #@lock.lock
    begin
      begin
        Dir.entries(@path_id).each  do |file|
          if file != '.' and file != '..'
            key = Key.new("")
            key.set_key_by_string(file)
            is_in_range = key.is_in_range(start_key, end_key, include_start_key, include_end_key)
            if (in_range && is_in_range) || (!in_range && !is_in_range)
              File.delete(@path_id + file)

              if block_given?
                yield(file)
              end
            end
          end
        end
      rescue Exception => e
        puts "\n"
        puts e.backtrace.join("\n")
        puts "[PersistentDB] Erro ao remover todos os arquivos fora da faixa."
        return false
      end
      return true
    ensure
      #@lock.unlock
    end

    
  end

  def qtd_files_range(start_key, end_key, include_start_key = true, include_end_key = true, in_range = true)
    #@lock.lock
    begin
      begin
        count = 0
        Dir.entries(@path_id).each  do |file|
          if file != '.' and file != '..'
            key = Key.new("")
            key.set_key_by_string(file)
            is_in_range = key.is_in_range(start_key, end_key, include_start_key, include_end_key)
            if (in_range && is_in_range) || (!in_range && !is_in_range)
              count += 1
            end
          end
        end
      rescue Exception => e
        puts "\n"
        puts e.backtrace.join("\n")
        puts "[PersistentDB] Erro ao remover todos os arquivos fora da faixa."
        return -1
      end
      return count
    ensure
      #@lock.unlock
    end    
  end

  # Incluir bloco chamado para cada arquivo.
  def insert_files(zip_file)
    #@lock.lock
    begin
      begin
        node_zip_file = PATH_BASE + @node_id + "documents.zip"

        file = File.open(node_zip_file, "wb")
        file.write(zip_file)
        file.close

        zip = Zip::ZipFile.open(node_zip_file)

        Zip::ZipFile.foreach(node_zip_file) do |file|
          if file.to_s != "documents/"
            extracted_file_key = file.to_s.delete("documents/")
            extracted_file_path = @path_id + extracted_file_key

            zip.extract(file.to_s, extracted_file_path)

            if block_given?
              extracted_file = File.open(extracted_file_path, "rb")
              extrated_file_content = extracted_file.read
              extracted_file.close

              yield(extracted_file_key, extrated_file_content)
            end
          end
        end

        File.delete(node_zip_file)
      rescue Exception => e
        puts "\n"
        puts e.backtrace.join("\n")
        puts "[PersistentDB] Erro ao adicionar varios arquivos de um zip: " + e.message
        return false
      end

      return true
    ensure
      #@lock.unlock
    end    
  end

  def replace_all_files(zip_file)
    #@lock.lock
    begin
      begin
        remove_all_files
        Dir.delete(@path_id)

        node_zip_file = PATH_BASE + @node_id + ".zip"

        file = File.open(node_zip_file, "wb")
        file.write(zip_file)
        file.close

        zip = Zip::ZipFile.open(node_zip_file)

        Zip::ZipFile.foreach(node_zip_file) do |file|
          zip.extract(file.to_s, @base_path_complement + file.to_s)
        end

        File.delete(node_zip_file)
      rescue Exception => e
        puts "\n"
        puts e.backtrace.join("\n")
        puts "[PersistentDB] Erro substituindo todo o banco: " + e.message
        return false
      end

      return true
    ensure
      #@lock.unlock
    end    
  end
  
  def destroy
    #@lock.lock
    begin
      remove_all_files
      Dir.delete(@path_id) if File.exist?(@path_id)
      @path_id = nil
    ensure
      #@lock.unlock
    end    
  end
end

